/*
 * The MIT License
 * 
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Martin Eigenbrodt
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package hudson.model;

import hudson.idservice.IdService;
import hudson.logging.HudsonTestLog;
import java.io.Serializable;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Builder;
import hudson.tasks.Recorder;
import hudson.tasks.Notifier;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;

import static hudson.model.Result.FAILURE;
import hudson.msservice.message_entry.EmailMessageEntry;
import java.io.*;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.charset.Charset;
import java.sql.*;
import java.util.*;
import java.util.logging.Level;
import net.sf.json.JSONObject;

/**
 * A build of a {@link Project}.
 *
 * <h2>Steps of a build</h2>
 * <p>
 * Roughly speaking, a {@link Build} goes through the following stages:
 *
 * <dl>
 * <dt>SCM checkout
 * <dd>Hudson decides which directory to use for a build, then the source code is checked out
 *
 * <dt>Pre-build steps
 * <dd>Everyone gets their {@link BuildStep#prebuild(AbstractBuild, BuildListener)} invoked
 * to indicate that the build is starting
 *
 * <dt>Build wrapper set up
 * <dd>{@link BuildWrapper#setUp(AbstractBuild, Launcher, BuildListener)} is invoked. This is normally
 * to prepare an environment for the build.
 *
 * <dt>Builder runs
 * <dd>{@link Builder#perform(AbstractBuild, Launcher, BuildListener)} is invoked. This is where
 * things that are useful to users happen, like calling Ant, Make, etc.
 *
 * <dt>Recorder runs
 * <dd>{@link Recorder#perform(AbstractBuild, Launcher, BuildListener)} is invoked. This is normally
 * to record the output from the build, such as test results.
 *
 * <dt>Notifier runs
 * <dd>{@link Notifier#perform(AbstractBuild, Launcher, BuildListener)} is invoked. This is normally
 * to send out notifications, based on the results determined so far.
 * </dl>
 *
 * <p>
 * And beyond that, the build is considered complete, and from then on {@link Build} object is there to
 * keep the record of what happened in this build. 
 *
 * @author Kohsuke Kawaguchi
 */
public abstract class Build <P extends Project<P,B>,B extends Build<P,B>>
    extends AbstractBuild<P,B> implements Serializable{

    /**
     * Creates a new build.
     */
    protected Build(P project) throws IOException {
        super(project);
    }

    protected Build(P job, Calendar timestamp) {
        super(job, timestamp);
    }

    /**
     * Loads a build from a log file.
     */
    protected Build(P project, File buildDir) throws IOException {
        super(project,buildDir);
    }

    /**
     * actions
     * the FreeStyleBuild extends the Build class and the FreeStyleBuild override the run method
     * this run method is complated in the Run class in the line 1330 and so-on
     */
    @Override
    public void run() {
        run(createRunner());
    }

    protected Runner createRunner() {
        return new RunnerImpl();
    }
    
    protected class RunnerImpl extends AbstractRunner implements Serializable{
        /**
         *@describe
         *  stopSignal:when user request http://127.0.0.1:8080/job/XXX/stop ,then set the stopSignal true
         *  when current step exeute finished ,the next build step will wait util the user request give 
         *  continue command
         *  mode:when user request url http://127.0.0.1/job/XXX/build?mode=ignoreerror or mode=suspenderrror or
         *  mode could get the value , when mode=ignoreerror build a project ignoreerror could execute all the steps ignore the 
         *  error ,when mode=suspenderror build a project suspend current build util user give a continue command continue 
         *  the build
         */
        Boolean stopSignal=false;
        
        /**
        *@describe 
        * setStopSignal was decleared in Queue'Excutable interface ,complete in Build,FreeStyleBuild,FreeStyleProject,MatrixBuild,MatrixRun 
        */
        public void setStopSignal(Boolean b){
           synchronized(stopSignal){
            stopSignal=b;
           }
        }
        
        protected Result doRun(BuildListener listener) throws Exception {
            /**
             *@describe
             *      Build extends AbstractBuild ,AbstractBuild extends Run ,Run has project(JobT)
             *      if the project has two buildteps ,when begin next buildstep should check the 
             *      forhead buildstep's exite status ,if false then the project build should abort
             */
            boolean isRight=preBuild(listener,project.getBuilders());
            if(!isRight)
                return FAILURE;
            if(!preBuild(listener,project.getPublishers()))
                return FAILURE;

            Result r = null;
            try {
                List<BuildWrapper> wrappers = new ArrayList<BuildWrapper>(project.getBuildWrappers().values());
                
                ParametersAction parameters = getAction(ParametersAction.class);
                if (parameters != null)
                    parameters.createBuildWrappers(Build.this,wrappers);

                for( BuildWrapper w : wrappers ) {
                    Environment e = w.setUp((AbstractBuild<?,?>)Build.this, launcher, listener);
                    if(e==null)
                        return (r = FAILURE);
                    buildEnvironments.add(e);
                }
                
                /**
                 *@describe
                 *      begin to build this pr
                 * oject , if one step execute error then exit this build abort this build
                 */
                if(!build(listener,project.getBuilders()))
                    r = FAILURE;
            } finally {
                if (r != null) setResult(r);
                // tear down in reverse order
                boolean failed=false;
                for( int i=buildEnvironments.size()-1; i>=0; i-- ) {
                    if (!buildEnvironments.get(i).tearDown(Build.this,listener)) {
                        failed=true;
                    }                    
                }
                // WARNING The return in the finally clause will trump any return before
                if (failed) return FAILURE;
            }
            
            return r;
        }

        public void post2(BuildListener listener) throws IOException, InterruptedException {
            if (!performAllBuildSteps(listener, project.getPublishers(), true))
                setResult(FAILURE);
            if (!performAllBuildSteps(listener, project.getProperties(), true))
                setResult(FAILURE);
        }

        @Override
        public void cleanUp(BuildListener listener) throws Exception {
            // at this point it's too late to mark the build as a failure, so ignore return value.
            performAllBuildSteps(listener, project.getPublishers(), false);
            performAllBuildSteps(listener, project.getProperties(), false);
            super.cleanUp(listener);
        }
        
                   
        /**
         * @param
         *      buildlistener: check the result of every steps execute
         *      steps: is all commandline step to execute
         * @describe
         *      buid project the next step will execute when the previous step execute is right
         */
        private boolean build(BuildListener listener, Collection<Builder> steps) throws IOException, InterruptedException,Exception, Exception {                     
            //ignore error , record how many errors ,then give current build success ,failure
            Map stepnum_exitecode_map = new HashMap();                                                                                     
            
            /**
             *when user get request give none data execute this  
             */
            if("".equals(project.getBuildMode()) || "aborterror".equals(project.getBuildMode())){                
                int step_num=0;         
                for( final BuildStep bs : steps ) {
                    if(!stopSignal){//not suspend
                        try{
                            step_num++;
                                                                                                              
                            /**
                             *set current step's step's number 
                             */
                            ((Builder)bs).setStepsNumber(step_num);
                            
                            /***
                            * since 2012/7/4 add a function that user could chose some steps to execute
                            * changed class is BatchFile's config.jelly and CommandInterpreter
                            */     
                            if(!((Builder)bs).getIsExecuteCurrent().equals("true")){
                                continue;
                            }
                                                                                                   
                            boolean is_right = perform(bs,listener);
                                                        
                           
                            //record current step execute result
                            stepnum_exitecode_map.put(step_num, is_right);
                                               
                            }catch(Exception e){
                                throw e;
                            }                        
                    }else{                       
                        while(stopSignal){
                            Thread.currentThread().sleep(1000);
                            Logger.getLogger(Build.class.getName()).log(HudsonTestLog.level,"stop");
                        }                                            
                        try{
                            step_num++;
                                                                              
                             /**
                             *set current step's step's number 
                             */
                            ((Builder)bs).setStepsNumber(step_num);
                            
                            /***
                            * since 2012/7/4 add a function that user could chose some steps to execute
                            * changed class is BatchFile's config.jelly and CommandInterpreter
                            */
                            if(!((Builder)bs).getIsExecuteCurrent().equals("true")){
                                continue;
                            }
                                                                     
                            
                            boolean is_right = perform(bs,listener);
                                                                             
                            //record current step execute result
                            stepnum_exitecode_map.put(step_num, is_right);
                                                       
                            }catch(Exception e){
                                throw e;
                        }
                    }
                    
                    //if has an false ,means current build is failure
                    if (stepnum_exitecode_map.containsValue(false)) {
                        return false;
                    }
                }   
                
                return true;
            }
            
            /**
             *ignore error when execute all the steps 
             */
            if("ignoreerror".equals(project.getBuildMode())){
                int step_num=0;                
                for( BuildStep bs : steps ) {
                    if(!stopSignal){
                        /**
                         *user has not stop current build 
                         */
                        try{
                                step_num++;
                                                                                              
                                /**
                                 *set current step's step's number 
                                 */
                                ((Builder)bs).setStepsNumber(step_num);
                                /***
                                * since 2012/7/4 add a function that user could chose some steps to execute
                                * changed class is BatchFile's config.jelly and CommandInterpreter
                                */
                                if(!((Builder)bs).getIsExecuteCurrent().equals("true")){
                                    continue;
                                }
                                                                            
                                
                                boolean is_right = perform(bs,listener);                                                              
                                
                                //record current step excute right ,wrong
                                stepnum_exitecode_map.put(step_num,is_right);
                                                                                                     
                            }catch(Exception e){
                               throw e;
                            }
                     }else{                      
                        while(stopSignal){
                          Thread.currentThread().sleep(1000);
                          Logger.getLogger(Build.class.getName()).log(HudsonTestLog.level,"stop");                         
                        }                      
                        /**
                         *when user continue current build
                         */
                        try{    
                                step_num++;
                                                                                            
                                /**
                                 *set current step's step's number 
                                 */
                                ((Builder)bs).setStepsNumber(step_num);
                                
                                 /***
                                * since 2012/7/4 add a function that user could chose some steps to execute
                                * changed class is BatchFile's config.jelly and CommandInterpreter
                                */
                                if(!((Builder)bs).getIsExecuteCurrent().equals("true")){
                                    continue;
                                }
                                                                                                                                
                                boolean is_right = perform(bs,listener);
                                                                                            
                                stepnum_exitecode_map.put(step_num,is_right);
                
                            }catch(Exception e){
                                throw e;
                        }
                    }
                }
                
                //if ignore error build , finished current build ,but if has an false ,means current build if failure
                if (stepnum_exitecode_map.containsValue(false)) {
                    return false;
                }
                return true;
            }
            
            /**
             *when occur an error suspend current build 
             */
            if("suspenderror".equals(project.getBuildMode())){
//                int step_num=0;               
                for(int step_num=0;step_num<project.getBuilders().size();){                   
                    if(!stopSignal){                  
                        try{   
                            Builder bs = project.getBuilders().get(step_num);
                            step_num++;
                                                                                   
                            //set current step's step's number                             
                            ((Builder)bs).setStepsNumber(step_num);
                            
                             /***
                            * since 2012/7/4 add a function that user could chose some steps to execute
                            * changed class is BatchFile's config.jelly and CommandInterpreter
                            */
                            if(!((Builder)bs).getIsExecuteCurrent().equals("true")){
                                continue;
                            }
                                                                                  
                            boolean is_right = perform((Builder)bs,listener);
                                                                                  
                            //record current step execute result
                            stepnum_exitecode_map.put(step_num, is_right); 
                                                      
                            }catch(Exception e){
                                throw e;
                            }
                     }else{
                        while(stopSignal){
                          Thread.currentThread().sleep(1000);
                          Logger.getLogger(Build.class.getName()).log(HudsonTestLog.level,"stop");                          
                        }
                        
                        try{
                            Builder bs = project.getBuilders().get(step_num);
                            step_num++;
                                                                                  
                            /**
                            *set current step's step's number 
                            */
                            ((Builder)bs).setStepsNumber(step_num);
                            
                            /***
                            * since 2012/7/4 add a function that user could chose some steps to execute
                            * changed class is BatchFile's config.jelly and CommandInterpreter
                            */
                            if(!((Builder)bs).getIsExecuteCurrent().equals("true")){
                                continue;
                            }                                                                           
                            
                            boolean is_right = perform((Builder)bs,listener);
                                                                                 
                            //record currend step execute result
                            stepnum_exitecode_map.put(step_num, is_right);
                                                      
                           }catch(Exception e){
                                throw e;
                        }
                    }
                    
                    //if there has one false , then suspend current build                  
                    if (stepnum_exitecode_map.containsValue(false)) {
                        step_num--;
                        setStopSignal(true);
                    }
                    
                }
                                                     
                //if there has one false , current build fail                  
                if (stepnum_exitecode_map.containsValue(false)) {
                    return false;
                }
                return true;
            }
                
            
            /**
             * @ since 2012/7/19 add debug execution mode 
             * when user chose this mode , execute one step then 
             * 
             */
            if("debugmode".equals(project.getBuildMode())){               
                for( int step_num=0;step_num<project.getBuilders().size(); ) {
                    if(!stopSignal){                  
                        try{
                            Builder bs = project.getBuilders().get(step_num);
                            step_num++;                                                      
                      
                            //set current step's step's number                         
                            ((Builder)bs).setStepsNumber(step_num);
                            
                             /***
                            * since 2012/7/4 add a function that user could chose some steps to execute
                            * changed class is BatchFile's config.jelly and CommandInterpreter
                            */
                            if(!((Builder)bs).getIsExecuteCurrent().equals("true")){
                                continue;
                            }
                                                                              
                            boolean is_right = perform(bs,listener);
                                                                                   
                            //record current step execute result
                            stepnum_exitecode_map.put(step_num, is_right); 
                           
                            
                            }catch(Exception e){
                                throw e;
                            }
                     }else{
                        while(stopSignal){
                          Thread.currentThread().sleep(1000);
                          Logger.getLogger(Build.class.getName()).log(HudsonTestLog.level,"It beagin to stop");                        
                        }
                        
                        try{
                            Builder bs = project.getBuilders().get(step_num);
                            step_num++;                          
                            
                            //set current step's step's number                       
                            ((Builder)bs).setStepsNumber(step_num);
                            
                            /***
                            * since 2012/7/4 add a function that user could chose some steps to execute
                            * changed class is BatchFile's config.jelly and CommandInterpreter
                            */
                            if(!((Builder)bs).getIsExecuteCurrent().equals("true")){
                                continue;
                            }
                                                       
                            boolean is_right = perform(bs,listener);
                                                                                
                            //record current step execute result
                            stepnum_exitecode_map.put(step_num, is_right);
                            
                           
                           }catch(Exception e){
                                throw e;
                        }
                    }
                    
                    /**
                    *all steps execute one step then stop untile user give a continue command
                    */
                   setStopSignal(true);
                    
                    //if there has one false , then suspend current build                  
                    if (stepnum_exitecode_map.containsValue(false)) {
                        step_num--;
                    }
                }
                
                //if there has one false , current build fail 
                if (stepnum_exitecode_map.containsValue(false)) {
                    return false;
                }
                return true;
            }
            
           return false; 
        }
    }
       
    private static final Logger LOGGER = Logger.getLogger(Build.class.getName());
}
